package com.daverin.FYFFest;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import java.util.TimerTask;
import java.util.Vector;

import android.app.Activity;
import android.app.Dialog;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.TimePicker;

public class FYFFest extends Activity {

  // This keeps the countdown running
  public class UpdateCountdown extends TimerTask {
    @Override
    public void run() {
      // Set the current countdown time.
      ((TextView)findViewById(R.id.countdown_seconds)).setText(
          String.format("%02d", time_left_in_seconds_ % 60));
      ((TextView)findViewById(R.id.countdown_minutes)).setText(
          String.format("%02d", time_left_in_seconds_ / 60 % 60));
      ((TextView)findViewById(R.id.countdown_hours)).setText(
          String.format("%d", time_left_in_seconds_ / (60 * 60) % 24));
      ((TextView)findViewById(R.id.countdown_days)).setText(
          String.format("%d", time_left_in_seconds_ / (24 * 60 * 60)));

      // Set the current message.
      ((TextView)findViewById(R.id.countdown_message_sender)).setText(decoded_user_id_);
      ((TextView)findViewById(R.id.countdown_message)).setText(decoded_message_);
      ((TextView)findViewById(R.id.countdown_band)).setText(decoded_band_);

      // Run again in a second.
      ui_update_handler_.postDelayed(new UpdateCountdown(), 100);
    }
  }

  private Vector<String> GetStringContentsOfURLString(String url_string) {
    Vector<String> return_set = new Vector<String>();
    URL target_url = null;
    try {
      target_url = new URL(url_string);
    } catch (MalformedURLException e) {
      Log.d("fyffest", "URL is badly formed: " + url_string);
      return_set.add("");
      return return_set;
    }
    HttpURLConnection url_connection = null;
    String return_string = "";
    try {
      url_connection = (HttpURLConnection) target_url.openConnection();
      InputStream in = new BufferedInputStream(url_connection.getInputStream());
      BufferedReader reader = new BufferedReader(new InputStreamReader(in));
      String next_line = reader.readLine();
      while (next_line != null) {
        return_set.add(next_line);
        next_line = reader.readLine();
      }
      Log.d("fyffest", "retreived contents: " + return_string);
    } catch (IOException e) {
      Log.d("fyffest", "IOException reading the next message: " + e.getMessage());
      return_set.clear();
      return_set.add("");
      return return_set;
    } finally {
      if (url_connection != null) {
        url_connection.disconnect();
      }
    }
    return return_set;
  }

  private void UpdateTimeLeft() {
    time_left_in_seconds_ = MillisecondsTilFYFFest() / 1000;
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      Log.d("fyffest", "Thread interrupted.");
    }
  }

  private void UpdateCurrentMessage() {
    try {
      Vector<String> message = GetStringContentsOfURLString(
          "http://fyffest-engine.appspot.com/get_latest_message");
      if (message.size() == 0) {
        // There was a failure, back off a bit, wait 20 seconds before the next one.
        Thread.sleep(20000);
      } else if (message.elementAt(0).length() == 0) {
        // There was a failure, back off a bit, wait 20 seconds before the next one.
        Thread.sleep(20000);
      } else {
        String[] message_pieces = message.elementAt(0).split(",", 3);
        if (message_pieces.length != 3) {
          Log.d("fyffest", "Message was retrieved but not formatted right.");
          return;
        }
        String decoded_user_id = null;
        String decoded_band = null;
        String decoded_message = null;
        try {
          decoded_user_id = URLDecoder.decode(message_pieces[0], "UTF-8");
          decoded_band = URLDecoder.decode(message_pieces[1], "UTF-8");
          decoded_message = URLDecoder.decode(message_pieces[2], "UTF-8");
        } catch (UnsupportedEncodingException e) {
          Log.d("fyffest", "Hard coded encoding is no good when decoding... bad news");
        }
        if ((decoded_user_id == null) || (decoded_band == null) || (decoded_message == null)) {
          // There was a failure in decoding, back off a bit.
          Thread.sleep(20000);
        }
        decoded_user_id_ = decoded_user_id;
        decoded_band_ = decoded_band;
        decoded_message_ = decoded_message;
      }
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      Log.d("fyffest", "Message retrieval sleep interrupted.");
    }
  }

  private void SetDateTimePickerValues(long time_left_in_millis) {
    GregorianCalendar chosen_time = new GregorianCalendar(time_zone_);
    chosen_time.setTimeInMillis(fyf_fest_time_.getTimeInMillis() - time_left_in_millis);
    
    ((DatePicker)date_time_picker_dialog_.findViewById(R.id.date_picker)).updateDate(
        chosen_time.get(Calendar.YEAR), chosen_time.get(Calendar.MONTH),
        chosen_time.get(Calendar.DAY_OF_MONTH));
    ((TimePicker)date_time_picker_dialog_.findViewById(R.id.time_picker)).setCurrentHour(
        chosen_time.get(Calendar.HOUR));
    ((TimePicker)date_time_picker_dialog_.findViewById(R.id.time_picker)).setCurrentMinute(
        chosen_time.get(Calendar.MINUTE));
  }

  String MakeTimeString(long time_left) {
    GregorianCalendar new_time = new GregorianCalendar(time_zone_);
    new_time.setTimeInMillis((long)(fyf_fest_time_.getTimeInMillis() - time_left * 1000.0));

    return str_months[new_time.get(Calendar.MONTH)] + " " +
        Integer.toString(new_time.get(Calendar.DAY_OF_MONTH)) + " " +
        Integer.toString(new_time.get(Calendar.HOUR)) + ":" +
        String.format("%02d", new_time.get(Calendar.MINUTE)) + ":" +
        String.format("%02d", new_time.get(Calendar.SECOND)) + " " +
        (new_time.get(Calendar.AM_PM) == Calendar.AM ? "AM" : "PM");

  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    time_zone_ = TimeZone.getTimeZone("America/Los_Angeles");
    fyf_fest_time_ = new GregorianCalendar(time_zone_);
    fyf_fest_time_.set(2011, 8, 3, 12, 0, 0);

    // Keep a counter going in a separate thread.
    new Thread() {
      public void run() {
        while (true) {
          UpdateTimeLeft();
        }
      }
    }.start();

    // Keep checking the url for new data in a separate thread.
    new Thread() {
      public void run() {
        while (true) {
          UpdateCurrentMessage();
        }
      }
    }.start();

    // This starts the countdown timer running, it will update every second.
    ui_update_handler_.postDelayed(new UpdateCountdown(), 0);

    // Attach the add message dialog to the add message button.
    ((Button)findViewById(R.id.countdown_add_message)).setOnClickListener(
        new View.OnClickListener() {
          public void onClick(View v) {
            showDialog(DIALOG_ADD_MESSAGE);
          }
        });

    // Attach the add message dialog to the add message button.
    ((Button)findViewById(R.id.countdown_show_stats)).setOnClickListener(
        new View.OnClickListener() {
          public void onClick(View v) {
            showDialog(DIALOG_BAND_STATS);
          }
        });
  }

  @Override
  protected Dialog onCreateDialog(int id) {
    switch(id) {
    case DIALOG_ADD_MESSAGE:
      return GetAddMessageDialog();
    case DIALOG_DATE_TIME_PICKER:
      return GetDateTimePickerDialog();
    case DIALOG_BAND_STATS:
      return GetBandStatsDialog();
    case DIALOG_ABOUT:
      return GetAboutDialog();
    }

    return super.onCreateDialog(id);
  }
  
  @Override
  protected void onPrepareDialog(int id, Dialog d) {
    switch(id) {
    case DIALOG_ADD_MESSAGE:
      PrepareAddMessageDialog();
      break;
    case DIALOG_DATE_TIME_PICKER:
      PrepareDateTimePickerDialog();
      break;
    case DIALOG_BAND_STATS:
      PrepareBandStatsDialog();
      break;
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    boolean result = super.onCreateOptionsMenu(menu);
    menu.add(1, MENU_ABOUT, 1, "About");
    return result;
  }
  
  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    case MENU_ABOUT:
      showDialog(DIALOG_ABOUT);
      return true;
    }
    return super.onOptionsItemSelected(item);
  }

  private class CancelDialogListener implements View.OnClickListener {
    public CancelDialogListener(int dialog_id) {
      dialog_id_ = dialog_id;
    }
    public void onClick(View v) {
      dismissDialog(dialog_id_);
    }
    int dialog_id_;
  }

  private class AddMessageToDataBaseListener implements View.OnClickListener {
    public void onClick(View v) {
      String time_message = "now=true";
      if (!chosen_time_is_now_) {
        time_message = "time_left=" + Long.toString(chosen_time_left_);
      }
      String user_id = ((EditText)add_message_dialog_.findViewById(R.id.message_id)).getText().toString();
      String message = ((EditText)add_message_dialog_.findViewById(R.id.message)).getText().toString();
      String band_name = ((Spinner)add_message_dialog_.findViewById(R.id.band_spinner)).getSelectedItem().toString();
      String encoded_user_id = null;
      String encoded_band_name = null;
      String encoded_message = null;
      try {
        encoded_user_id = URLEncoder.encode(user_id, "UTF-8");
        encoded_band_name = URLEncoder.encode(band_name, "UTF-8");
        encoded_message = URLEncoder.encode(message, "UTF-8");
      } catch (UnsupportedEncodingException e) {
        Log.d("fyffest", "Hardcoded encoding is unsupported.  Bad news.");
      }
      if ((encoded_band_name == null) || (encoded_user_id == null) || (encoded_message == null)) {
        dismissDialog(DIALOG_ADD_MESSAGE);
        return;
      }
      Log.d("fyffest", "Add new messages by " + encoded_user_id + " with band " +
          encoded_band_name + " with time portion " + time_message + ": " +
          encoded_message);
      Vector<String> response = GetStringContentsOfURLString(
          "http://fyffest-engine.appspot.com/add_message?" + time_message + 
          "&user_id=" + encoded_user_id + "&band=" + encoded_band_name + 
          "&message=" + encoded_message);
      if (response.size() == 0) {
        Log.d("fyffest", "Didn't get any response, strange condition");
      } else if (response.elementAt(0) == "OK") {
        Log.d("fyffest", "Added message successfully");
      } else {
        Log.d("fyffest", "Got error message: " + response.elementAt(0));
      }
      dismissDialog(DIALOG_ADD_MESSAGE);
    }
  }

  private class SetTimeToNowListener implements View.OnClickListener {
    public void onClick(View v) {
      chosen_time_is_now_ = true;
      ((TextView)add_message_dialog_.findViewById(R.id.time_view)).setText("Now");
      Log.d("fyffest", "time is set to now");
      dismissDialog(DIALOG_DATE_TIME_PICKER);
    }
  }

  private class SetTimeListener implements View.OnClickListener {
    public void onClick(View v) {
      chosen_time_is_now_ = false;

      // Retrieve the numerical time set by the pickers.
      int year = 2011;
      int month = ((DatePicker)date_time_picker_dialog_.findViewById(R.id.date_picker)).getMonth();
      int day = ((DatePicker)date_time_picker_dialog_.findViewById(R.id.date_picker)).getDayOfMonth();
      int hour = ((TimePicker)date_time_picker_dialog_.findViewById(R.id.time_picker)).getCurrentHour();
      int minute = ((TimePicker)date_time_picker_dialog_.findViewById(R.id.time_picker)).getCurrentMinute();

      // The current time, in the Los Angeles time zone.
      GregorianCalendar now = new GregorianCalendar(time_zone_);
      now.set(year, month, day, hour, minute, 0);

      // Get the difference between now and then in milliseconds.
      chosen_time_left_ = (fyf_fest_time_.getTimeInMillis() - now.getTimeInMillis()) / 1000;

      ((TextView)add_message_dialog_.findViewById(R.id.time_view)).setText(
          MakeTimeString(chosen_time_left_));

      Log.d("fyffest", "time is set to " + Long.toString(chosen_time_left_));
      dismissDialog(DIALOG_DATE_TIME_PICKER);
    }
  }

  private Dialog GetAddMessageDialog() {
    if (add_message_dialog_ == null) {
      add_message_dialog_ = new Dialog(this);
      add_message_dialog_.requestWindowFeature(Window.FEATURE_NO_TITLE);
      add_message_dialog_.setContentView(R.layout.add_message_layout);
      ((Button)add_message_dialog_.findViewById(R.id.cancel_button)).
          setOnClickListener(new CancelDialogListener(DIALOG_ADD_MESSAGE));
      ((Button)add_message_dialog_.findViewById(R.id.ok_button)).
          setOnClickListener(new AddMessageToDataBaseListener());
      ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
          this, R.array.bands_array, android.R.layout.simple_spinner_item);
      adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
      ((Spinner)add_message_dialog_.findViewById(R.id.band_spinner)).
          setAdapter(adapter);
      ((Button)add_message_dialog_.findViewById(R.id.set_time_button)).
          setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
              showDialog(DIALOG_DATE_TIME_PICKER);
            }
          });
    }
    return add_message_dialog_;
  }
  
  private Dialog GetBandStatsDialog() {
    if (band_stats_dialog_ == null) {
      Log.d("fyffest", "Creating band stats dialog");
      band_stats_dialog_ = new Dialog(this);
      band_stats_dialog_.requestWindowFeature(Window.FEATURE_NO_TITLE);
      band_stats_dialog_.setContentView(R.layout.show_stats_layout);
      ((Button)band_stats_dialog_.findViewById(R.id.ok_button)).
          setOnClickListener(new CancelDialogListener(DIALOG_BAND_STATS));
    }
    return band_stats_dialog_;
  }

  private Dialog GetDateTimePickerDialog() {
    if (date_time_picker_dialog_ == null) {
      date_time_picker_dialog_ = new Dialog(this);
      date_time_picker_dialog_.requestWindowFeature(Window.FEATURE_NO_TITLE);
      date_time_picker_dialog_.setContentView(R.layout.date_time_picker_layout);
      ((Button)date_time_picker_dialog_.findViewById(R.id.cancel_button)).
          setOnClickListener(new CancelDialogListener(DIALOG_DATE_TIME_PICKER));
      ((Button)date_time_picker_dialog_.findViewById(R.id.set_time_button)).
          setOnClickListener(new SetTimeListener());
      ((Button)date_time_picker_dialog_.findViewById(R.id.now_button)).
          setOnClickListener(new SetTimeToNowListener());
      
    }
    return date_time_picker_dialog_;
  }
  
  private Dialog GetAboutDialog() {
    if (about_dialog_ == null) {
      about_dialog_ = new Dialog(this);
      about_dialog_.requestWindowFeature(Window.FEATURE_NO_TITLE);
      about_dialog_.setContentView(R.layout.about_layout);
      ((Button)about_dialog_.findViewById(R.id.ok_button)).
          setOnClickListener(new CancelDialogListener(DIALOG_ABOUT));
    }
    return about_dialog_;
  }

  private void PrepareAddMessageDialog() {
    add_message_dialog_.getWindow().setLayout(LinearLayout.LayoutParams.FILL_PARENT,
        LinearLayout.LayoutParams.WRAP_CONTENT);
    chosen_time_is_now_ = true;
    if (chosen_time_is_now_) {
      ((TextView)add_message_dialog_.findViewById(R.id.time_view)).setText("Now");
    } else {
      ((TextView)add_message_dialog_.findViewById(R.id.time_view)).setText(
          MakeTimeString(chosen_time_left_));
    }
  }

  private void PrepareBandStatsDialog() {
    band_stats_dialog_.getWindow().setLayout(LinearLayout.LayoutParams.FILL_PARENT,
        LinearLayout.LayoutParams.WRAP_CONTENT);
    Log.d("fyffest", "Preparing band stats dialog");
    Vector<String> stats_table = GetStringContentsOfURLString(
        "http://fyffest-engine.appspot.com/get_stats");
    Log.d("fyffest", "Got " + Integer.toString(stats_table.size()) + " stats entries.");
    TableLayout table_layout = (TableLayout)band_stats_dialog_.findViewById(R.id.show_stats_table);
    table_layout.removeAllViews();
    for (int i = 0; i < stats_table.size(); ++i) {
      String[] split_string = stats_table.elementAt(i).split(",", 3);
      if (split_string.length != 3) {
        Log.d("fyffest", "Stats table string is badly formatted: " + stats_table.elementAt(i));
        continue;
      }
      
      TableRow stats_row = new TableRow(this);
      stats_row.setLayoutParams(new LinearLayout.LayoutParams(
          LinearLayout.LayoutParams.FILL_PARENT,
          LinearLayout.LayoutParams.WRAP_CONTENT));
      
      TextView band_name = new TextView(this);
      TableRow.LayoutParams band_name_params = new TableRow.LayoutParams(
          TableRow.LayoutParams.WRAP_CONTENT,
          TableRow.LayoutParams.WRAP_CONTENT);
      band_name_params.leftMargin = 20;
      band_name_params.rightMargin = 2;
      band_name_params.topMargin = 5;
      band_name_params.weight = (float)1.0;
      band_name.setLayoutParams(band_name_params);
      band_name.setTextSize(TypedValue.COMPLEX_UNIT_PT, (float)9.0);
      band_name.setTextColor(Color.rgb(152, 68, 68));
      band_name.setText(split_string[0]);
      band_name.setGravity(Gravity.CENTER);
      stats_row.addView(band_name);
      
      double percentage = Double.parseDouble(split_string[2]);
      TextView band_percentage = new TextView(this);
      TableRow.LayoutParams band_percentage_params = new TableRow.LayoutParams(
          TableRow.LayoutParams.WRAP_CONTENT,
          TableRow.LayoutParams.WRAP_CONTENT);
      band_percentage_params.rightMargin = 20;
      band_percentage_params.leftMargin = 10;
      band_percentage_params.topMargin = 5;
      band_percentage.setLayoutParams(band_percentage_params);
      band_percentage.setTextSize(TypedValue.COMPLEX_UNIT_PT, (float)9.0);
      band_percentage.setTextColor(Color.rgb(68, 152, 68));
      band_percentage.setGravity(Gravity.CENTER);
      band_percentage.setText(String.format("%.1f%%", percentage * 100));
      stats_row.addView(band_percentage);
      
      table_layout.addView(stats_row);
    }
  }

  private void PrepareDateTimePickerDialog() {
    date_time_picker_dialog_.getWindow().setLayout(LinearLayout.LayoutParams.FILL_PARENT,
        LinearLayout.LayoutParams.WRAP_CONTENT);
    if (chosen_time_is_now_) {
      SetDateTimePickerValues(MillisecondsTilFYFFest());
    } else {
      SetDateTimePickerValues(chosen_time_left_ * 1000);
    }
  }
  
  private long MillisecondsTilFYFFest() {
    // The current time, in the Los Angeles time zone.
    GregorianCalendar now = new GregorianCalendar(time_zone_);

    // Get the difference between now and then in milliseconds.
    return fyf_fest_time_.getTimeInMillis() - now.getTimeInMillis();
  }

  //An array of months, used for displaying dates in a human-readable format.
  String[] str_months = new String[]{
      "Jan", "Feb", "Mar", "Apr", "May", "Jun",
      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

  private static final int DIALOG_ADD_MESSAGE = 1000;
  private static final int DIALOG_DATE_TIME_PICKER = 1001;
  private static final int DIALOG_BAND_STATS = 1002;
  private static final int DIALOG_ABOUT = 1003;
  
  private static final int MENU_ABOUT = 100;

  private Dialog add_message_dialog_;
  private Dialog date_time_picker_dialog_;
  private Dialog band_stats_dialog_;
  private Dialog about_dialog_;

  private boolean chosen_time_is_now_ = true;
  private long chosen_time_left_ = 0;
  
  private Handler ui_update_handler_ = new Handler();

  private TimeZone time_zone_;
  private GregorianCalendar fyf_fest_time_;

  private long time_left_in_seconds_ = 0;
  private String decoded_user_id_ = "";
  private String decoded_band_ = "";
  private String decoded_message_ = "";
}